<?
class tokkotai
{
	private $dbh = null;
	private $db_host = null;
	private $db_user = null;
	private $db_password = null;
	private $db_db = null;

	private $svn = null;
	private $svnlook = null;

	private $default_search = array
	(
		'repos' => null,
		'user' => null,
		'path' => null,
		'log' => null,
		'date_option' => 'hour',
		'hours' => 3,
		'only_match' => false
	);

	private $file_types = array
	(
		'c' => 'c',
		'css' => 'css',
		'h' => 'c',
		'htaccess' => 'apache',
		'htm' => 'html4strict',
		'html' => 'html4strict',
		'js' => 'javascript',
		'mysql' => 'mysql',
		'php' => 'php',
		'pl' => 'perl',
		'py' => 'python',
		'sh' => 'bash',
		'sql' => 'sql',
		'tpl' => 'smarty',
		'txt' => 'text',
		'xml' => 'xml'
	);

	private $mime_types = array
	(
		'jpg' => 'image/jpg',
		'png' => 'image/png',
		'gif' => 'image/gif'
	);

	private $geshi_line_numbers = false;
	private $filename = null;
	private $revision = null;

	const SESS_GROUP = 'tok';

	/**
	* parses in config variables
	*/
	public function __construct()
	{
		if(PHP_VERSION < 5.1)
		{
			echo  'You are running PHP ' . PHP_VERSION . '. >= 5.1 is required';
			exit;
		}
		if(!file_exists('config.php'))
		{
			echo 'config.php file not found!';
			exit;
		}
		include_once('config.php');
		if(isset($config))
		{
			foreach($config as $k => $v)
			{
				if(property_exists($this,$k))
				{
					if(is_string($v))
					{
						$this->$k = $v;
					}
				}
			}
		}
	}

	/**
	* uses svnlook to view
	* @param	int $id resource id
	* @param	boolean $prev view previous revision
	*/
	public function view($id,$prev=false)
	{
		if(!$r = $this->get_resource($id))
		{
			return null;
		}
		if($prev)
		{
			$rev = $r['rev'] - 1;
		}
		else
		{
			$rev =& $r['rev'];
		}
		if($ext = $this->get_file_extension($r['resource']))
		{
			$cmd = $this->svnlook . ' cat -r ' . $rev . ' ' . $r['repository'] . ' ' . $r['resource'];
			$cmd = escapeshellcmd($cmd);
			$file = shell_exec($cmd);

			include_once(dirname(__FILE__) . '/geshi/geshi.php');
			$_geshi = new GeSHi($file,$this->file_types[$ext]);
			if($this->geshi_line_numbers)
			{
				$_geshi->enable_line_numbers(GESHI_NORMAL_LINE_NUMBERS);
			}
			return $_geshi->parse_code();
		}
	}

	/**
	* displays a binary file from svnlook based on allowed mimetypes
	*/
	public function view_bin($id,$prev=false)
	{
		if(!$r = $this->get_resource($id))
		{
			return null;
		}
		if($prev)
		{
			$rev = $r['rev'] - 1;
		}
		else
		{
			$rev =& $r['rev'];
		}
		if($mime = $this->get_mimetype($r['resource']))
		{
			$cmd = $this->svnlook . ' cat -r ' . $rev . ' ' . $r['repository'] . ' ' . $r['resource'];
			$cmd = escapeshellcmd($cmd);
			header('content-type: ' . $mime);
			passthru($cmd);
		}

	}

	/**
	* show diff of given resource
	* @param	int $id resource_id
	*/
	public function diff($id)
	{
		if(!$r = $this->get_resource($id))
		{
			return null;
		}	
		$cmd = $this->svnlook . ' diff -r ' . $r['rev'] . ' ' . $r['repository'];
		$cmd = escapeshellcmd($cmd);
		exec($cmd,$out);
		$i = 0;
		$num = count($out);
		$start = false;
		while($i < $num)
		{
			if($out[$i] == 'Modified: ' . $r['resource'])
			{
				$start = true;
				$file = $out[$i] . "\n";
			}
			else
			{
				if($start)
				{
					if(isset($out[$i + 1]) && preg_match('/^=======/',$out[$i + 1]))
					{
						break;
					}
					$file .= $out[$i] . "\n";
				}
			}
			++$i;
		}
		include_once(dirname(__FILE__) . '/geshi/geshi.php');
		$_geshi = new GeSHi($file,'diff');
		return $_geshi->parse_code();
	}

	/**
	* loads class variables for given resource
	* @param	int $id resource_id
	*/
	public function load_resource($id)
	{
		$this->get_resource($id);
	}

	/**
	* returns row for given resource id
	* @param	int $id resource_id
	* @return	array
	*/
	private function get_resource(&$id)
	{
		$sql = 'SELECT
				b.repository,
				a.resource,
				a.rev_id,
				c.rev
			FROM resource a
			LEFT JOIN repository b ON a.rep_id = b.id
			LEFT JOIN revision c ON a.rev_id = c.id
			WHERE a.id = ' . (int) $id;
		$r = $this->db_getrow($sql);
		$this->filename = $r['resource'];
		$this->revision = (int) $r['rev'];
		return $r;
	}

	/**
	* returns $this->filename
	* @return	string
	*/
	public function return_filename()
	{
		return $this->filename;
	}

	/**
	* returns $this->revision
	* @param	boolean $prev returns previous revision
	* @return	int
	*/
	public function return_revision($prev=false)
	{
		if($prev)
		{
			return $this->revision - 1;
		}
		return $this->revision;
	}

	/**
	* returns filename extension if in prescribed list
	*/
	public function get_file_extension($filename)
	{
		if(preg_match('/(\.)([\w]+)$/',$filename,$suffix))
		{
			if(array_key_exists($suffix[2],$this->file_types))
			{
				return $suffix[2];
			}
		}
	}

	/**
	* returns mimetype of file if in prescribed list
	*/
	public function get_mimetype($filename)
	{
		if(preg_match('/(\.)([\w]+)$/',$filename,$suffix))
		{
			if(isset($this->mime_types[$suffix[2]]))
			{
				return $this->mime_types[$suffix[2]];
			}
		}
	}

	/**
	* amalgamates the form input into a search session
	* @param	array $input (GET/POST)
	*/
	public function create_search_session(array $input)
	{
		$search = $this->default_search;
		if(is_array($input['repos']))
		{
			if(in_array(0,$input['repos']))
			{
				$search['repos'] = null;
			}
			else
			{
				$search['repos'] = $input['repos'];
			}
		}
		if(is_array($input['user']))
		{
			if(in_array(0,$input['user']))
			{
				$search['user'] = null;
			}
			else
			{
				$search['user'] = $input['user'];
			}
		}
		if(!empty($input['path']))
		{
			$search['path'] = trim($input['path']);
		}
		if(!empty($input['log']))
		{
			$search['log'] = trim($input['log']);
		}
		if(!empty($input['date_option']))
		{
			$search['date_option'] = $input['date_option'];
			if($search['date_option'] == 'between')
			{
				if(!empty($input['d1']) && !empty($input['d2']))
				{
					$search['d1'] = trim($input['d1']);	
					$search['d2'] = trim($input['d2']);	
				}
			}	
		}
		if(!empty($input['hours']))
		{
			$search['hours'] = (int) trim($input['hours']);
		}
		if(!empty($input['only_match']))
		{
			$search['only_match'] = true;
		}
		else
		{
			$search['only_match'] = false;
		}
		$_SESSION[self::SESS_GROUP]->search = $search;	
	}

	/**
	* return search
	* @return	array
	*/
	public function get_search()
	{
		if(isset($_SESSION[self::SESS_GROUP]->search))
		{
			return $_SESSION[self::SESS_GROUP]->search;
		}
		return $this->default_search;
	}
	
	/**
	* parse search path into resource list
	* @param	$s
	* @return	string
	*/
	public function parse_path($s)
	{
		$ss = str_replace('.','\.',$_SESSION[self::SESS_GROUP]->search['path']);
		return preg_replace('#(' . $ss . ')#','<span class="hl">$1</span>',$s);
	}

	/**
	* return array of svn users/authors
	* @param	int $rep_id
	* @return	array
	*/
	public function get_authors($rep_id=null)
	{
		if(empty($rep_id) && !empty($_SESSION[self::SESS_GROUP]->search['repos']))
		{
			$rep_id = $_SESSION[self::SESS_GROUP]->search['repos'];
		}
		$sql = 'select id, user from author';
		if($rep_id)
		{
			$sql .= ' where rep_id = ' . (int) $rep_id;
		}
		$sql .= ' order by user';
		return $this->db_getall($sql);
	}

	/**
	* return array of svn repositories
	* @return	array
	*/
	public function get_repositories()
	{
		$sql = 'select id, alias as name from repository';
		return $this->db_getall($sql);
	}

	/**
	* establish database connection handle
	*/
	private function db_connect()
	{
		if(!$this->dbh)
		{
			$this->dbh = mysql_connect($this->db_host,$this->db_user,$this->db_password);
			mysql_select_db($this->db_db,$this->dbh);
		}
	}

	/**
	* mysql_real_escapes a string
	* @param	string $s
	* @return	string
	*/
	private function db_e($s)
	{
		$this->db_connect();
		return mysql_real_escape_string($s,$this->dbh);
	}

	/**
	* send query to mysql
	* @param	string $sql
	* @return	object
	*/
	private function db_query(&$sql)
	{
		$this->db_connect();
		return mysql_query($sql,$this->dbh);
	}

	/**
	* return a single value resultset
	* @param	string $sql
	* @return	string
	*/
	private function db_getone($sql)
	{
		if(!$query = $this->db_query($sql))
		{
			return null;
		}
		if($r = mysql_fetch_row($query))
		{
			return $r[0];
		}
	}

	/**
	* return a single row resultset
	* @param	string $sql
	* @return	string
	*/
	private function db_getrow($sql)
	{
		if(!$query = $this->db_query($sql))
		{
			return null;
		}
		return mysql_fetch_assoc($query);
	}

	/**
	* return an array resultset
	* @param	string $sql
	* @return	array
	*/
	private function db_getall($sql)
	{
		if(!$query = $this->db_query($sql))
		{
			return null;
		}
		$return = array();
		while($r = mysql_fetch_assoc($query))
		{
			array_push($return,$r);
		}
		return $return;
	}

	/**
	* return 2 array resultsets: one multidimensional array of whole query and one single-dimensional array of
	* given field with duplicates removed.
	* @param	string $sql
	* @param	string $field field to write to indexed array
	* @return	array
	*/
	private function db_getall_multi($sql,$field)
	{
		if(!$query = $this->db_query($sql))
		{
			return null;
		}
		$multi = array();
		$single = array();
		while($r = mysql_fetch_assoc($query))
		{
			array_push($multi,$r);
			array_push($single,$r[$field]);
		}
		array_unique($single);
		return array($multi,$single);
	}

	/** 
	* returns a multidimensional associative array indexed on specified field
	* @param        string $sql
	* @param        string $id specified field
	* @return       array
	*/
	private function db_getall_id($sql,$id)
	{
		if(!$query = $this->db_query($sql))
		{
			return null;
		}
		$ret = array();
		$i = 0;
		while($row = mysql_fetch_assoc($query)) 
		{   
			if(!isset($current_id) || $current_id != $row[$id])
			{   
				$current_id = $row[$id];
				$i = 0;
			}   
			else
			{   
				++$i;
			}   
			foreach($row as $key => $val) 
			{   
				if($key != $id)
				{   
					$ret[$row[$id]][$i][$key] = $val;
				}   
			}   
		}   
		return $ret;
	}

	/**
	* use svn to look up file path for autocompletion
	* @param	int $repos_id repository id
	* @param	string $path file path
	* @param	int $rev
	* @return	array
	*/
	public function lookup_path($repos_id,$path,$rev=null)
	{
		if(empty($rev))
		{
			$rev = 'HEAD';
		}
		else
		{
			$rev = (int) $rev;
		}
		$repos = $this->get_repos_path($repos_id);	
		$dir = dirname($path);
		$file = basename($path);
		$cmd = $this->svn . ' list -r ' . $rev . ' file:///' . $repos . '/' . $dir;
		$cmd = escapeshellcmd($cmd);
		exec($cmd,$files);
		$i = 0;
		$num = count($files);
		$matches = array();
		$return = array(array());
		while($i < $num)
		{
			array_push($return[0],$files[$i]);	
			if(preg_match('/^' . str_replace('.','\.',$file) . '/',$files[$i]))
			{
				array_push($matches,$files[$i]);
			}
			++$i;
		}
		if(count($matches) == 1)
		{
			if($dir != '.')
			{
				$return[1] = $dir . '/' . $matches[0];
			}
			else
			{
				$return[1] =& $matches[0];
			}
			if(preg_match('#/$#',$return[1]))
			{
				$cmd = $this->svn . ' list -r ' . $rev . ' file:///' . $repos . '/' . $return[1];
				$cmd = escapeshellcmd($cmd);
				exec($cmd,$lu);
				$return[0] =& $lu;
				$return[2] =& $return[1];
			}
		}
		if(!isset($return[2]))
		{
			$return[2] = preg_replace('#^\.#','',$dir);
			if(!empty($return[2]))
			{
				$return[2] .= '/';
			}
		}
		return $return;
	}

	/**
	* use svn to look up file path
	* @param	int $repos_id repository id
	* @param	string $path file path
	* @param	int $rev
	* @return	array
	*/
	public function lookup_dir($repos_id,$path,$rev=null)
	{
		if(empty($rev))
		{
			$rev = 'HEAD';
		}
		else
		{
			$rev = (int) $rev;
		}
		$repos = $this->get_repos_path($repos_id);	
		if(substr($path,-1) == '/')
		{
			$dir = $path;
		}
		else
		{
			$dir = dirname($path);
		}
		$cmd = $this->svn . ' list -r ' . $rev . ' file:///' . $repos . '/' . $dir;
		$cmd = escapeshellcmd($cmd);
		exec($cmd,$files);
		$i = 0;
		$num = count($files);
		$return = array
		(
			0 => $files,
			1 => $path
		);
		$return[2] = preg_replace('#^\.#','',$dir);
		return $return;
	}

	/**
	* return file path to given repository
	* @param	int $id repository id
	* @return	string
	*/
	private function get_repos_path(&$id)
	{
		$sql = 'select repository from repository where id = ' . (int) $id;
		return $this->db_getone($sql);
	}

	/**
	* do search query
	* @return	array
	*/
	public function do_search()
	{
		$search = $this->get_search();
		switch($search['date_option'])
		{
			case 'hour':
				$t1 = strtotime('-' . (int) $search['hours'] . ' hour');
				break;
			case 'day':
				$t1 = strtotime('-1 day');
				break;
			case 'week':
				$t1 = strtotime('-1 week');
				break;
			case 'month':
				$t1 = strtotime('-1 month');
				break;
			case 'year':
				$t1 = strtotime('-1 year');
				break;
			case 'all':
				break;
			case 'between':
				if(!empty($search['d1']) && !empty($search['d2']))
				{
					$t1 = strtotime($search['d1'] . ' 00:00:00');
					$t2 = strtotime($search['d2'] . ' 23:59:59');
				}
				break;
		}
		$sql = 'SELECT 
				a.date, 
				c.user, 
				b.alias, 
				a.rev, 
				a.rep_id, 
				d.resource, 
				d.action, 
				a.log 
			FROM revision a
			LEFT JOIN repository b on a.rep_id = b.id
			LEFT JOIN author c on a.author_id = c.id
			LEFT JOIN resource d on a.id = d.rev_id 
			WHERE a.id is not null';
		if(!empty($search['user']))
		{
			$sql .= ' AND a.author_id IN (' . implode(',',$search['user']) . ')';
		}
		if(!empty($search['repos']))
		{
			$sql .= ' AND a.rep_id IN (' . implode(',',$search['repos']) . ')';
		}
		if(isset($t1))
		{
			if(!isset($t2))
			{
				$t2 = time();
			}
			$sql .= ' AND a.date BETWEEN "' . date('Y-m-d H:i:s',$t1) . '" AND "' . date('Y-m-d H:i:s',$t2) . '"';
		}
		if(!empty($search['path']))
		{
			$sql .= ' AND d.resource LIKE "%' . $this->db_e($search['path']) . '%"';
		}
		if(!empty($search['log']))
		{
			$sql .= ' AND a.log LIKE "%' . $this->db_e($search['log']) . '%"';
		}
		$sql .= ' GROUP BY a.id ORDER BY a.rev DESC';
		$result = $this->db_getall_multi($sql,'rev');

		$sql = 'SELECT
				rev_id,
				id,
				resource,
				action
			FROM resource
			WHERE rev_id IN (' . implode(',',$result[1]) . ')';
		if(!empty($search['path']) && $search['only_match'])
		{
			$sql .= ' AND resource LIKE "%' . $this->db_e($search['path']) . '%"';
		}
		$files = $this->db_getall_id($sql,'rev_id');
		return array
		(
			'result' => $result[0],
			'files' => $files
		);
	}
}
?>
